package no.hig.ekm.ircclient;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.List;
import java.util.ResourceBundle;

import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JInternalFrame;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;

import jerklib.Channel;
import jerklib.Session;

/**
 * A class which is responsible for a single channelWindow. The channel
 * window can either be a server_messages window, or a ordinary IRC-channel
 * or it can be a private-chat window.
 * 
 * @author eigil, morten
 */
public class ChannelWindow extends JInternalFrame {
	private static final long serialVersionUID = 1L;
	
	//Static ResourceBundle, used for internationalized text-strings
	private static ResourceBundle messages;
	//Constants, to determine the window-type
	public static int UNKNOWN = 0;
	public static int SERVER_MESSAGES = 1;
	public static int CHANNEL_CHAT = 2;
	
	public static int PRIVATE_CHAT = 3; 

	//Local properties of the window.
	private String channelName, channelMode, topic;
	private Session session;
	private int type;
	
	//GUI-Components.
	private JPanel mainPanel, sendPanel;
	private JTextField message;
	private JTextPane chat;
	private JScrollPane chatScroll;
	private JButton send;
	private NickList listModel;
	private JPopupMenu popupMenu;
	private JList<String> nickList;
	
	/**
	 * The constructor creates the window, and generates the correct layot. It does
	 * also set up the abstract-action used to send messages, and defines the send
	 * button to be the default button.
	 * 
	 * @param s - A sessionobject, with an established connection to a IRC-server
	 * @param sn - Servername. A string identifying the hostname of the server.
	 * @param cn - Channelname. A string identifying the name of the channel, or the
	 * 				nick of the other part in a private message.
	 * @param windowType - An int, identifying the type of this window.
	 */
	ChannelWindow(Session s, String head, String cn, int windowType) {
		super(head, true, true, true, true);
		
		//Store the properties
		channelName = cn;
		session = s;
		type = windowType;
		channelMode = new String();
		topic = new String();
		
		this.generateLayout();
		this.setActionListeners();
		
		addInternalFrameListener(new Closing());
		//Sets the default button. ie: which button to click if the user
		//  hits the enter key.
		getRootPane().setDefaultButton(send);
	}
	
	/**
	 * Method which adds a new line in the bottom of the chat-window, 
	 * using the default font.
	 * 
	 * @param line - String to be added. Without ending linefeed.
	 */
	public void addChatLine(String line) {
		this.addChatLine(line, Fonts.getFont(Fonts.DEFAULT));
	}
	
	/**
	 * Method which adds a new line in the bottom of the chat-windo, and
	 * styles it with the supplied SimpleAttributeSet.
	 * 
	 * @param line - String containing the text to be added
	 * @param sas - SimpleAttributeSet containing the style of the text.
	 */
	public void addChatLine(String line, SimpleAttributeSet sas) {			
		SwingUtilities.invokeLater (new MyThread(line, sas));
		
	    SwingUtilities.invokeLater (new Thread() {
	        public void run () {
	        	// Get the scrollbar from the scroll pane
	        	JScrollBar scrollbar = chatScroll.getVerticalScrollBar ();
	        	// Set the scrollbar to its maximum value
	        	scrollbar.setValue (scrollbar.getMaximum());
	        }
	    });
	    if (!ChannelWindow.this.isSelected) {
	    if (type == SERVER_MESSAGES)
			MainChannelTree.colorNode(ChannelWindow.this.toString(), "");
		else if (type == CHANNEL_CHAT || type == PRIVATE_CHAT)
			MainChannelTree.colorNode(session.getConnectedHostName(), ChannelWindow.this.toString());
	    }
	}
	
	/**
	 * Method to add a nick to the nicklist.
	 * 
	 * @param nick - The nick, as a String
	 */
	public void addNick(String nick) {
		listModel.add(nick);
	}
	
	/**
	 * Method to change modes to the channel. Either adds/removes a mode from
	 * a user, or from the channel itself.
	 * 
	 * @param mode - char representing the mode
	 * @param add - boolean value representing if the mode is + (true) or - (false)
	 * @param arg - String representing an eventual parameter
	 */
	public void channelMode(char mode, boolean add, String arg) {
		if(mode == 'v' || mode == 'o') {
			listModel.nickMode(arg, mode, add);
		} else {
			if(channelMode.indexOf(mode) == -1 && add) {
				channelMode = channelMode + mode;
			} else {
				String tmp = new String();
				for(int i = 0; i < channelMode.length(); i++)
					if(channelMode.charAt(i) != mode)
						tmp = tmp + channelMode.charAt(i);
				channelMode = tmp;
			}
			updateHead();
		}
	}
	
	/**
	 * Method to generate the window's UI.
	 */
	private void generateLayout() {
		//Create the main panel, and use BorderLayout.
		mainPanel = new JPanel();
		mainPanel.setLayout(new BorderLayout());
		
		//Add the text-area used to display incoming messaged, and
		//  make it in-editable.
		chat = new JTextPane();
		chat.setEditable(false);
		chatScroll = new JScrollPane(chat);
		mainPanel.add(chatScroll, BorderLayout.CENTER);
		
		//If this is a channel-chat window, add a list of nicks to
		//  the right of the window
		if(type == CHANNEL_CHAT)
			setupNicklist();
		
		//Add a textfield, and a send-button to the bottom of the window
		sendPanel = new JPanel();
		sendPanel.setLayout(new BorderLayout());
		sendPanel.add(message = new JTextField(), BorderLayout.CENTER);
		sendPanel.add(send = new JButton(), BorderLayout.EAST);
		
		mainPanel.add(sendPanel, BorderLayout.SOUTH);
		
		//Finalize the window, and set it visible.
		this.add(mainPanel);
		this.setSize(600, 400);
		this.setVisible(true);
	}
	
	/**
	 * Gets the channel name
	 * @return the channelName
	 */
	public String getChannelName() {
		return channelName;
	}

	/**
	 * Get the channel's session
	 * @return the session
	 */
	public Session getSession() {
		return session;
	}
	
	/**
	 * Method which updates the nicklist, when an existing nick changes his name.
	 * 
	 * @param from - String representing the old nick
	 * @param to - String representing the new nick
	 */
	public void nickChanged(String from, String to) {
		listModel.nickChanged(from, to);
	}
	
	/**
	 * Method which removes an nick entirely from the nicklist.
	 * Used when people leaves a channel.
	 * 
	 * @param nick - String representing the nick
	 */
	public void removeNick(String nick) {
		listModel.remove(nick);
	}
	
	/**
	 * Method which should handle the response of a user clicking on
	 * the send button. Determine if it is a command, or a message
	 * and sending it.
	 */
	public void sendButtonPushed() {
		String txt = message.getText();
		
		//If we are in a channel-chat window
		if(type == ChannelWindow.CHANNEL_CHAT) {
			if(txt.startsWith("/")) {
				if(txt.startsWith("/me")) {
					session.getChannel(channelName).action(txt.substring(4));
					this.addChatLine("* " + session.getNick() + " " + txt.substring(4), Fonts.getFont(Fonts.ACTIONS));
				} else if (txt.startsWith("/kick")) {
					String nick, reason;
					reason = txt.substring(6);
					if(reason.indexOf(" ") == -1) {
						nick = reason;
						reason = new String("");
					} else {
						nick = reason.substring(0, reason.indexOf(" ") - 1);
						reason = reason.substring(reason.indexOf(" "));
					}
					session.getChannel(channelName).kick(nick, reason);
				} else if (txt.startsWith("/op")) {
					String nick = txt.substring(4);
					session.getChannel(channelName).op(nick);
				} else if (txt.startsWith("/deop")) {
					String nick = txt.substring(6);
					session.getChannel(channelName).deop(nick);
				} else if (txt.startsWith("/voice")) {
					String nick = txt.substring(7);
					session.getChannel(channelName).voice(nick);
				} else if (txt.startsWith("/devoice")) {
					String nick = txt.substring(9);
					session.getChannel(channelName).deVoice(nick);
				} else if (txt.startsWith("/part")) {
					session.getChannel(channelName).part(txt.substring(5));
				} else if (txt.startsWith("/join")) {
					String nick = txt.substring(6);
					session.join(nick);
				} else if (txt.startsWith("/q")) {
					ServerEventListener sel = null;
					Session s = ChannelWindow.this.session;
					
					Object[] ircel = s.getIRCEventListeners().toArray();
					for(int i = 0; i < ircel.length; i++) {
						if(ircel[i] instanceof ServerEventListener)
							 sel = (ServerEventListener) ircel[i];
					}
					
					if(sel == null)
						return;
					
					String nick = txt.substring(3);
					nick = nick.substring(1);					
					sel.privMessageWindow(s, nick);
					
				}
				
			} else {
				session.getChannel(channelName).say(txt);
				this.addChatLine("<" + getPrefixedNick(session.getNick()) + "> " + txt, Fonts.getFont(Fonts.MYOWNTEXT));
			}
		} else if(type == ChannelWindow.PRIVATE_CHAT) {
			if(txt.startsWith("/")) {
				if(txt.startsWith("/me")) {
					session.getChannel(channelName).action(txt.substring(4));
					this.addChatLine("* " + session.getNick() + " " + txt.substring(4), Fonts.getFont(Fonts.ACTIONS));
				} else if(txt.startsWith("/exit")) {
					dispose();
				}				
			} else {
				this.addChatLine("<" + session.getNick() + "> " + txt, Fonts.getFont(Fonts.MYOWNTEXT));
				session.sayPrivate(channelName, txt);
			}
		} else if(type == ChannelWindow.SERVER_MESSAGES) {
			//TODO: Handle commands different from messages
			if(txt.startsWith("/")) {
				session.sayRaw(txt.substring(1));
				this.addChatLine(txt, Fonts.getFont(Fonts.MYOWNTEXT));
			}
		}
		
		//Reset the textfield, and set the focus on it.
		message.setText("");
		message.requestFocusInWindow();
	}
	
	/**
	 * Method responsible to add action-listener to the button.
	 */
	private void setActionListeners() {
		//Define the action
		AbstractAction snd = new AbstractAction("Send") {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				sendButtonPushed();
			}
		};
		
		//Add it to the send-button
		send.setAction(snd);
	}
	
	/**
	 * Method which updates the list of nicks, and sorts i by name and nick-type.
	 * 
	 * @param all - List<String> containing all the nicks in the nicklist
	 * @param ops - List<String> containing all the operators in the nicklist
	 * @param voices - List<String> containing all the voices in the nicklist
	 */
	public void setNickList(List<String> all, List<String> ops, List<String> voices) {
		listModel.updateNicks(all, ops, voices);
	}
	
	/**
	 * Changes the channel-topic
	 * 
	 * @param topic
	 */
	public void setTopic(String topic) {
		this.topic = topic;
		updateHead();
	}
	
	/**
	 * Method which generates the nicklist, and adds it to the channelWindow.
	 */
	public void setupNicklist() {
		JMenuItem menuItem;
		nickList = new JList<String>(listModel = new NickList());
		mainPanel.add(new JScrollPane(nickList), BorderLayout.EAST);
		
		
		AbstractAction op = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				boolean op = false;
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("@")) {
					nick = nick.substring(1);
					op = true;
				} else if(nick.startsWith("+"))
					nick = nick.substring(1);
				
				if(!op)
					ChannelWindow.this.session.getChannel(ChannelWindow.this.channelName).op(nick);
				else
					ChannelWindow.this.session.getChannel(ChannelWindow.this.channelName).deop(nick);	
			}
		};
		AbstractAction voice = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				boolean voice = false;
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("+")) {
					nick = nick.substring(1);
					voice = true;
				} else if(nick.startsWith("@"))
					nick = nick.substring(1);
				
				if(!voice)
					ChannelWindow.this.session.getChannel(ChannelWindow.this.channelName).voice(nick);
				else
					ChannelWindow.this.session.getChannel(ChannelWindow.this.channelName).deVoice(nick);
			}
		};
		AbstractAction kick = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("+") || nick.startsWith("@"))
					nick = nick.substring(1);
				
				ChannelWindow.this.session.getChannel(ChannelWindow.this.channelName).kick(nick, "");
			}
		};
		AbstractAction ban = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("+") || nick.startsWith("@"))
					nick = nick.substring(1);
				
				ChannelWindow.this.session.getChannel(ChannelWindow.this.channelName).mode("-ov+b " + nick);
			}
		};
		AbstractAction privmsg = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				ServerEventListener sel = null;
				Session s = ChannelWindow.this.session;
				
				Object[] ircel = s.getIRCEventListeners().toArray();
				for(int i = 0; i < ircel.length; i++) {
					if(ircel[i] instanceof ServerEventListener)
						 sel = (ServerEventListener) ircel[i];
				}
				
				if(sel == null)
					return;
				
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("+") || nick.startsWith("@"))
					nick = nick.substring(1);
				
				sel.privMessageWindow(s, nick);
			}
		};
		AbstractAction whois = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("+") || nick.startsWith("@"))
					nick = nick.substring(1);
				
				session.whois(nick);
			}
		};
		AbstractAction slap = new AbstractAction() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				String nick = ChannelWindow.this.nickList.getSelectedValue();
				if(nick.startsWith("+") || nick.startsWith("@"))
					nick = nick.substring(1);
				
				session.getChannel(channelName).action("slaps " + nick + " around a bit with a large trout!");
				addChatLine("* " + session.getNick() + " slaps " + nick + " around a bit with a large trout!", 
						Fonts.getFont(Fonts.ACTIONS));
			}
		};
		
		//Creating items in the popup menu and adds the actionlisteners
		popupMenu = new JPopupMenu();
		menuItem = popupMenu.add(messages.getString("nickListRightClickOp"));
		menuItem.addActionListener(op);
		menuItem = popupMenu.add(messages.getString("nickListRightClickVoice"));
		menuItem.addActionListener(voice);
		menuItem = popupMenu.add(messages.getString("nickListRightClickKick"));
		menuItem.addActionListener(kick);
		menuItem = popupMenu.add(messages.getString("nickListRightClickBan"));
		menuItem.addActionListener(ban);
		popupMenu.addSeparator();
		menuItem = popupMenu.add(messages.getString("nickListRightClickPriv"));
 		menuItem.addActionListener(privmsg);
		menuItem = popupMenu.add(messages.getString("nickListRightClickWhois"));
 		menuItem.addActionListener(whois);
		popupMenu.addSeparator();
		menuItem = popupMenu.add(messages.getString("nickListRightClickSlap"));
		menuItem.addActionListener(slap);
				
		nickList.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				rightclick(e);
			}
			public void mousePressed(MouseEvent e) {
				rightclick(e);
			}
			public void mouseReleased(MouseEvent e) {
				rightclick(e);
			}
			
			public void rightclick(MouseEvent e) {
				if (e.isPopupTrigger()) {	//if the event shows the menu
					nickList.setSelectedIndex(nickList.locationToIndex(e.getPoint()));	//select the item
					popupMenu.show(nickList, e.getX(), e.getY());						//and show the menu
				}
			}			
		});
	}
	
	/**
	 * Method to represent an object of this type as a string. Used primarly
	 * by the channel-tree.
	 * 
	 * @see java.awt.Component#toString()
	 */
	public String toString() {
		if(type == ChannelWindow.PRIVATE_CHAT) {
			return channelName;
		} else if (type == ChannelWindow.SERVER_MESSAGES) {
			return session.getConnectedHostName();
		} else if (type == ChannelWindow.CHANNEL_CHAT) {
			return channelName;
		}
		return null;
	}
	
	/**
	 * Method to refresh the header of a channelwindow
	 */
	public void updateHead() {
		setTitle(channelName + " - " + topic + " (" + channelMode + ")");
		repaint();
	}

	/**
	 * Method which sets the internationalization ResourceBundle
	 * 
	 * @param m - ResourceBundle, ready to be used.
	 */
	public static void setMessages(ResourceBundle m) {
		ChannelWindow.messages = m;
	}
	
	/**
	 * Method which returns the supplied nick, prefxed by an eventual
	 * op/voice prefix
	 * 
	 * @param nick - String representing the nick
	 * @return - String representing the prefixed nick
	 */
	public String getPrefixedNick(String nick) {
		return listModel.getPrefixedNick(nick);
	}
	
	/**
	 * Internal thread, used to add a chatline to the windows.
	 * 
	 * @author eigil
	 */
	class MyThread extends Thread {
		String line;
		SimpleAttributeSet sas;

		public MyThread(String l, SimpleAttributeSet s) {
			line = l;
			sas = s;
		}
		
		@Override
		public void run() {
			int pos = chat.getStyledDocument().getEndPosition().getOffset();
        	try {
        		// add the text to the document
        		chat.getStyledDocument().insertString(pos, line+"\n", sas);
        	} catch (BadLocationException ble) {
        		ble.printStackTrace();
        	}
		}
	}
	class Closing implements InternalFrameListener {
		@Override
		public void internalFrameActivated(InternalFrameEvent e) {
			
			if (ChannelWindow.this.isSelected) {
				/*
				 * TODO A bug: When there is only one channel Window, and we minimize the Server window, 
				 * the channel w. gets activated and "selected" though it does not get the focus
				 */
				//System.err.println("ACTIVATED and Selected FRAME: " + ChannelWindow.this.toString() + " Type= " + type);
				if (type == SERVER_MESSAGES) {
					MainChannelTree.findNode(ChannelWindow.this.toString(), "");
				} else if (type == CHANNEL_CHAT || type == PRIVATE_CHAT) {
					
					MainChannelTree.findNode(session.getConnectedHostName(), ChannelWindow.this.toString());
				}
			}
		}

		@Override
		public void internalFrameClosed(InternalFrameEvent e) {
		}

		@Override
		public void internalFrameClosing(InternalFrameEvent e) {
			//Determine the type of the window and properly dispose of it
			if (type == CHANNEL_CHAT){
				System.out.println("Closing the chat window");
				if (session != null) {
					session.getChannel(channelName).part("Bye bye!");
					Main.win.tree.removeChannel(session.getConnectedHostName(), ChannelWindow.this);
				} else
					dispose();
				//TODO Add event handling when user manually types "/part"
			}
			else if (type == SERVER_MESSAGES) {
				System.out.println("Closing the SERVER window");
				Main.win.tree.removeChannel(session.getConnectedHostName(), ChannelWindow.this);
				for(Channel ch : session.getChannels()) {
					session.getChannel(ch.getName()).part("Quitting...");
				}
				
				session.sayRaw("quit"); //TODO Change to proper "quit" command
			}
		}

		@Override
		public void internalFrameDeactivated(InternalFrameEvent e) {
			//System.out.println("DEACTIVATED SERVER window");
		}

		@Override
		public void internalFrameDeiconified(InternalFrameEvent e) {
			//Fix the bug: losing default functionality when changing window states
			getRootPane().setDefaultButton(send);
		}

		@Override
		public void internalFrameIconified(InternalFrameEvent e) {
		}

		@Override
		public void internalFrameOpened(InternalFrameEvent e) {
			System.err.println("OPENED FRAME!");
		}
	}
}